using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace OWED.Document
{
    /// <summary>
    /// Absolute paths allow you to "save" a node in a string, and
    /// refind it later.  The string format is as follows
    /// 
    /// root/#/child1/#/child2/#/node
    /// 
    /// Where, 
    ///   root is the root node of this structure
    ///   child1 is a first level child of root
    ///   child2 is a second level child of root
    ///   node is the node this object "points" to
    /// 
    /// All # are zero referenced sibling counts.  For instance in
    /// &lt;body&gt;
    ///   &lt;text&gt;Blah&lt;text&gt;
    ///   &lt;id&gt;5&lt;text&gt;
    /// &lt;body&gt;
    /// 
    /// The tag "id" would be /body/0/id/1/ because body has no
    /// predecessor children, and id has one predecessor child.
    /// </summary>
    public class AbsolutePath
    {

        String path;
        List<int> positions;

        /// <summary>
        /// Constructs an AbsolutePath for this node object.
        /// </summary>
        /// <param name="node"></param>
        public AbsolutePath(XmlNode node)
        {
            path = "";
            positions = new List<int>();
            while (node != null)
            {
                int pos = getPos(node);
                positions.Add(pos);
                path = node.Name + "/" + pos.ToString() + "/" + path;
                node = node.ParentNode;
            }
        }

        /// <summary>
        /// Construct an absolute path from a string object.
        /// </summary>
        /// <param name="pathP">Path to use</param>
        public AbsolutePath(String pathP)
        {
            path = pathP;
        }

        /// <summary>
        /// Constructing an absolute path that is an extension than another
        /// </summary>
        /// <param name="old">the old path</param>
        /// <param name="node">the name of the node to add</param>
        /// <param name="pos">the position of the node to add</param>
        public AbsolutePath(AbsolutePath old, String node, int pos): this(old.path)
        {
            positions = null;
            path += "/" + node + "/" + pos.ToString();
        }

        /// <summary>
        /// Construct an absolute path that is part of another
        /// </summary>
        /// <param name="old">the old one</param>
        /// <param name="minuslevels">the number of levels to remove on the end</param>
        public AbsolutePath(AbsolutePath old, int minuslevels): this(old.path)
        {
            minuslevels *= 2;
            int last = 0;
            for (int i = path.Length-1; i >= 0; --i)
            {
                if (path[i] == '/')
                    --minuslevels;
                last = i;
                if (minuslevels == 0)
                    break;
            }
            path = path.Substring(0, last);
        }

        /// <summary>
        /// Return the zero indexed position of this node among 
        /// it's siblings.
        /// </summary>
        /// <param name="node">Node to find the position of</param>
        /// <returns>Position of node among it's siblings, or zero if no siblings</returns>
        private int getPos(XmlNode node)
        {
            int pos = 0;
            node = node.PreviousSibling;
            while (node != null)
            {
                node = node.PreviousSibling;
                pos++;
            }
            return pos;
        }

        /// <summary>
        /// Get the node represented by this absolute path in the
        /// document.
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public XmlNode getNode(XmlDocument document)
        {
            if (positions != null)
            {
                return getNodeFromPositions(document);
            }
            else
            {
                return getNodeFromString(document);
            }
        }

        /// <summary>
        /// Searches tho document based on the positions list.
        /// </summary>
        /// <param name="document">document to search</param>
        /// <returns>node "pointed" by this path</returns>
        private XmlNode getNodeFromPositions(XmlDocument document)
        {
            XmlNode cur = document;
            XmlNode prev = null;
            foreach (int count in positions)
            {
                //move count siblings
                for (int pos = 0; pos < count; pos++)
                {
                    cur = cur.NextSibling;
                }
                //then go to a child
                prev = cur;
                cur = cur.FirstChild;
            }
            return prev;
        }

        /// <summary>
        /// This implements a trivial hand parser to find all numbers
        /// and add them to positions, then it calls 
        /// getNodeFromPositions to get the actual XmlNode.
        /// </summary>
        /// <param name="document">document to search</param>
        /// <returns>The node in this document "pointed" by the path</returns>
        private XmlNode getNodeFromString(XmlDocument document)
        {
            positions = new List<int>();
            String[] parse = path.Split('/');

            Boolean inName = true;
            foreach (String s in parse)
            {
                if (s != "")
                {
                    if (!inName)
                    {
                        int pos;
                        Int32.TryParse(s, out pos);
                        positions.Add(pos);
                    }
                    inName = !inName;
                }
            }
            return getNodeFromPositions(document);
        }

        /// <summary>
        /// Returns the string representation of this path.  See class
        /// documentation for details of format.
        /// </summary>
        /// <returns></returns>
        public override String ToString()
        {
            return path;
        }

    }
}
